﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using DotNetNuke.Entities.Users;
using System.Xml.Linq;
using System.Collections.Specialized;

namespace BrandonHaynes.Membership
	{
	/// <summary>
	/// A control used to display prompts for a user's missing credential values
	/// 
	/// This class also providers static helper methods to allow multi-factor authentication in DotNetNuke with
	/// as few codebehind changes as possible.  Insofar as is possible, all logic is moved out of the control
	/// codebehind into this class.
	/// </summary>
	public abstract class MultifactorControl : UserControl
		{
		public object DataSource { get; set; }

		/// <summary>
		/// This prefix is used to identify values that should be inserted into the credential constructed
		/// after a postback; only those POSTed values starting with this prefix will be included.
		/// </summary>
		public static string PromptPrefix { get { return "__credential"; } }

		/// <summary>
		/// Determines if the user requires multifactor authentication, and, if so, determines if the authentication
		/// was marked as incomplete (and we therefore require additional information)
		/// </summary>
		/// <param name="user"></param>
		/// <returns></returns>
		public static bool IsIncompleteMultifactorUser(UserInfo user)
			{
			var multifactorUser = user as MultifactorUser;
			return multifactorUser != null ? IsIncompleteMultifactorUser(multifactorUser) : false;
			}

		private static bool IsIncompleteMultifactorUser(MultifactorUser user)
			{
			// A user is incomplete if and only if it has one or more missing credential values
			return user.MissingCredentials.Any();
			}

		/// <summary>
		/// Given a source control and a user, databinds that source control against the user's missing credentials
		/// and attaches to the parent control.
		/// 
		/// Note that the source control must be of type MultifactorControl, and the user of type MultifactorUser.
		/// We don't require strong-typing here to ease the number of casts used in the client.
		/// </summary>
		/// <param name="sourceControl">The source control (which must be of type MultifactorControl!)</param>
		/// <param name="parentControl">The parent control, to which the source is attached</param>
		/// <param name="user">The user to use as a databinding source</param>
		public static void Attach(Control sourceControl, Control parentControl, UserInfo user)
		{ Attach((MultifactorControl)sourceControl, parentControl, (MultifactorUser)user); }

		private static void Attach(MultifactorControl multifactorControl, Control parentControl, MultifactorUser multifactorUser)
			{
			// Add the multifactor control to its parent
			parentControl.Controls.Add(multifactorControl);

			// And databind against the user's missing credentials (this will create the prompts)
			multifactorControl.DataSource = multifactorUser.MissingCredentials;
			multifactorControl.DataBind();
			}

		/// <summary>
		/// Given a collection (usually a POSTed form), constructs an xml string of the values contained therein,
		/// and appends the verificationCode to the data.  Note that this method looks for the 
		/// MultifactorClient.PromptPrefix value to identify which elements are to be included.
		/// </summary>
		public static string CreateVerificationData(NameValueCollection data, string verificationCode)
			{
			var content = data
				.Keys.Cast<string>()
				.Where(key => key.Contains(PromptPrefix))
				.Select(key => new XElement(key.SubstringAfter(PromptPrefix), data[key]));
			var rootElement = new XElement("root", new XElement("verificationCode", verificationCode), content);

			return rootElement.ToString();
			}
		}
	}
